/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-05-25     vandoul       the first version
 */

#include <rtthread.h>
#include <sys/socket.h>
#include <sys/select.h>
//#include <lwip/sockets.h>

#define LOG_TAG           "SRV_TCP"
#define LOG_LVL           LOG_LVL_DBG
#include <ulog.h>

#include "user_config.h"
#include "srv_tcp.h"

#define SRV_TCP_EVENT_REQ_READY             1
#define SRV_TCP_EVENT_RSP_READY             2

struct srv_tcp_object {
    rt_bool_t is_init;
    int sock_fd;
    char name[32];
    rt_thread_t tid;
    rt_event_t srv_evt;
    rt_mutex_t srv_lock;
    char addr[16];
    int port;
    struct srv_tcp_data *data;
};

static struct srv_tcp_object srv_tcp_obj[SRV_TCP_MAX_NUM];

static void srv_tcp_task(void *args)
{
    struct srv_tcp_object *tcp_obj = (struct srv_tcp_object *)args;
    LOG_D("tcp task run. %s, %s:%d", tcp_obj->name, tcp_obj->addr, tcp_obj->port);
    while(1) {
        rt_event_recv(tcp_obj->srv_evt, SRV_TCP_EVENT_REQ_READY, RT_EVENT_FLAG_OR|RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, RT_NULL);
        struct srv_tcp_data *data = tcp_obj->data;
        if(data && data->req.buff && (data->req_size > 0)) {
            if(data->addr == RT_NULL) {
                data->addr = tcp_obj->addr;
                data->port = tcp_obj->port;
            }
            do {
                if(tcp_obj->sock_fd < 0) {
                    tcp_obj->sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                    if(tcp_obj->sock_fd == -1) {
                        data->rsp_size = -1;
                        break;
                    }
                }
                LOG_I("sock fd:%d, req size:%d", tcp_obj->sock_fd, data->req_size);
                struct sockaddr_in addr;
                rt_memset(&addr, 0, sizeof(addr));
                addr.sin_family = AF_INET;
                addr.sin_port = htons(data->port);
                addr.sin_addr.s_addr = inet_addr(data->addr);
                if(connect(tcp_obj->sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
                    data->rsp_size = -1;
                    break;
                }
                send(tcp_obj->sock_fd, data->req.buff, data->req_size, 0);
                if(data->rsp.buff && (data->rsp_size > 0)) {
                    fd_set rdfds;
                    struct timeval timeout;
                    timeout.tv_sec = data->timeout_s;
                    timeout.tv_usec = data->timeout_us;
                    FD_ZERO(&rdfds);
                    FD_SET(tcp_obj->sock_fd, &rdfds);
                    int ret = select(tcp_obj->sock_fd+1, &rdfds, RT_NULL, RT_NULL, &timeout);
                    if(ret <= 0) {
                        data->rsp_size = -1;
                        break;
                    }
                    if(ret > 0) {
                        ret = recv(tcp_obj->sock_fd, data->rsp.buff, data->rsp_size, 0);
                        if(ret < 0) {
                            data->rsp_size = -1;
                        } else {
                            data->rsp_size = ret;
                        }
                    }
                } else {
                    data->rsp_size = 0;
                }
            } while(0);
            if(tcp_obj->sock_fd >= 0) {
                closesocket(tcp_obj->sock_fd);
                tcp_obj->sock_fd = -1;
            }
            rt_event_send(tcp_obj->srv_evt, SRV_TCP_EVENT_RSP_READY);
        } else if(data) {
            LOG_E("data:%p,data->req.buff:%p,data->req_size:%d",data,data->req.buff,data->req_size);
            data->rsp_size = -1;
            rt_event_send(tcp_obj->srv_evt, SRV_TCP_EVENT_RSP_READY);
        }
    }
}
int srv_tcp_create(struct srv_tcp_config *cfg)
{
    if(cfg == RT_NULL) {
        return -1;
    }
    int index;
    char name[32];
    struct srv_tcp_object *tcp_obj = RT_NULL;
    for(index = 0; index < SRV_TCP_MAX_NUM; index++) {
        tcp_obj = &srv_tcp_obj[index];
        if(tcp_obj->is_init){
            continue;
        }
        tcp_obj->is_init = RT_TRUE;
        rt_sprintf(name, "tcplock%d", index);
        tcp_obj->srv_lock = rt_mutex_create(name, RT_IPC_FLAG_PRIO);
        if(tcp_obj->srv_lock == RT_NULL) {
            break;
        }
        rt_sprintf(name, "tcpevt%d", index);
        tcp_obj->srv_evt = rt_event_create(name, RT_IPC_FLAG_FIFO);
        if(tcp_obj->srv_evt == RT_NULL) {
            break;
        }
        int priority = cfg->priority;
        if(priority < 0) {
            priority = RT_MAIN_THREAD_PRIORITY;
        }
        rt_sprintf(name, "tcptask%d", index);
        tcp_obj->tid = rt_thread_create(name, srv_tcp_task, tcp_obj, 2048, priority, 10);
        if(tcp_obj->tid == RT_NULL) {
            break;
        }
        return index;
    }
    if(index < SRV_TCP_MAX_NUM) {
        if(tcp_obj->srv_lock) {
            rt_mutex_delete(tcp_obj->srv_lock);
            tcp_obj->srv_lock = RT_NULL;
        }
        if(tcp_obj->srv_evt) {
            rt_event_delete(tcp_obj->srv_evt);
            tcp_obj->srv_evt = RT_NULL;
        }
        if(tcp_obj->tid) {
            rt_thread_delete(tcp_obj->tid);
            tcp_obj->tid = RT_NULL;
        }
        if(tcp_obj->is_init) {
            tcp_obj->is_init = RT_FALSE;
        }
    }
    return -1;
}
int srv_tcp_trans(int srv_num, struct srv_tcp_data *data)
{
    if((srv_num >= 0) && (srv_num < SRV_TCP_MAX_NUM) && (data != RT_NULL)) {
        struct srv_tcp_object *tcp_obj = &srv_tcp_obj[srv_num];
        if(!tcp_obj->is_init) {
            return -1;
        }
        rt_mutex_take(tcp_obj->srv_lock, RT_WAITING_FOREVER);
        tcp_obj->data = data;
        rt_event_send(tcp_obj->srv_evt, SRV_TCP_EVENT_REQ_READY);
        rt_event_recv(tcp_obj->srv_evt, SRV_TCP_EVENT_RSP_READY, RT_EVENT_FLAG_OR|RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, RT_NULL);
        rt_mutex_release(tcp_obj->srv_lock);
        return RT_EOK;
    }
    return -RT_ERROR;
}
int srv_tcp_start(int srv_num)
{
    if(srv_num < SRV_TCP_MAX_NUM) {
        struct srv_tcp_object *tcp_obj = &srv_tcp_obj[srv_num];
        if(tcp_obj->tid) {
            return rt_thread_startup(tcp_obj->tid);
        }
    }
    return -RT_ERROR;
}
int srv_tcp_set_name(int srv_num, char *name)
{
    if(srv_num < SRV_TCP_MAX_NUM) {
        struct srv_tcp_object *tcp_obj = &srv_tcp_obj[srv_num];
        int len = rt_strlen(name);
        if(len < 16) {
            rt_memcpy(tcp_obj->name, name, len);
            tcp_obj->name[len] = '\0';
            return RT_EOK;
        }
    }
    return -RT_ERROR;
}
int srv_tcp_set_addr(int srv_num, char *addr, int port)
{
    if(srv_num < SRV_TCP_MAX_NUM) {
        struct srv_tcp_object *tcp_obj = &srv_tcp_obj[srv_num];
        int len = rt_strlen(addr);
        if((len > 0 ) && (len < 32)) {
            rt_memcpy(tcp_obj->addr, addr, len);
            tcp_obj->addr[len] = '\0';
        }
        if(port>=0) {
            tcp_obj->port = port;
        }
        return RT_EOK;
    }
    return -RT_ERROR;
}
int srv_tcp_get_srv_num(char *name)
{
    if(name == RT_NULL) {
        return -1;
    }
    for(int i=0; i<SRV_TCP_MAX_NUM; i++) {
        if(rt_strcmp(name, srv_tcp_obj[i].name) == 0) {
            return i;
        }
    }
    return -1;
}
/*************************************************************************************/

void srv_tcp_test_task(void *args)
{
    rt_tick_t last_tick;
    uint8_t req_buff[64];
    uint8_t rsp_buff[64];
    struct srv_tcp_data data = {
        /* .addr        = */"192.168.4.25",
        /* .port        = */11223,
        /* .req_size    = */11,
        /* .req.buff    = */req_buff,
        /* .rsp_size    = */sizeof(rsp_buff),
        /* .rsp.buff    = */rsp_buff,
        /* .timeout_s   = */2,
        /* .timeout_us  = */0,
    };
    srv_tcp_start(0);
    while(1) {
        last_tick = rt_tick_get();
        rt_memcpy(req_buff, "hello world", 13);
        data.req_size = rt_strlen(req_buff);
        data.rsp_size = sizeof(rsp_buff);
        if(srv_tcp_trans(0, &data) == RT_EOK) {
            LOG_I("rsp size:%d", data.rsp_size);
            if(data.rsp_size > 0) {
                rt_memcpy(data.req.buff, data.rsp.buff, data.rsp_size);
                data.req_size = data.rsp_size;
                data.rsp_size = 0;
                if(srv_tcp_trans(0, &data) != RT_EOK) {
                    LOG_E("retrans failed!");
                }
            }
        } else {
            LOG_E("tcp trans failed!");
        }
        rt_thread_delay_until(&last_tick, rt_tick_from_millisecond(1000));
    }
}
int srv_tcp_init(void)
{
    static struct srv_tcp_config tcp_config[1] = {
        {
            /* .priority = */-1
        }
    };
    for(int i=0; i<sizeof(tcp_config)/sizeof(tcp_config[0]); i++) {
        srv_tcp_create(&tcp_config[i]);
    }
    rt_thread_t tid = rt_thread_create("tcp_test", srv_tcp_test_task, NULL, 2048, RT_MAIN_THREAD_PRIORITY, 10);
    if(tid != RT_NULL) {
        rt_thread_startup(tid);
    } else {
        LOG_E("create tcp_test failed!");
    }
}
//INIT_APP_EXPORT(srv_tcp_init);
